"""
Mail Handler class contains formatting functionaliy to convert mail
to html
Written By: Daniel Ortiz
"""
import re, datetime, markdown, wx
from database.mapclass import *
from email.MIMEText import MIMEText
from email.MIMEBase import MIMEBase
from email.MIMEMultipart import MIMEMultipart
from email import Encoders
from twisted.internet import reactor, defer, protocol, ssl
from twisted.mail import smtp
from twisted.python.failure import Failure
try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO

class MailStateHandler(object):
    """Handles the state of the delivery of mail between the desktop and the server"""
    def __init__(self, parent):
        self.parent = parent
        self.Variables()
        
    def Variables(self):
        """Initiate class wide variables"""
        self.feedback = 'Feedback'
        self.no_feedback = 'No Feedback'
        self.vis = None
        
    def BindVisualization(self, vis):
        """Bind the current visualization to the handler"""
        self.vis = vis
        
    def BindDatabaseHandler(self, h):
        """Bind to visualization"""
        self.database_handler = h
        
    def OnCompose(self, event):
        """Creates a new empty page in the notebook"""
        d, vis = self.database_handler.GetCurrentMailbox()
        if d:
            data = vis.GetCurrentMailData()
            new_from = d.username + '@' + d.domain
            mail = data['Mail']
            mailbox = data['Mailbox']
            vis.NewIncompleteMail(from_who = new_from, mailbox = d, mail = mail, mailbox = mailbox)
        else:
            self.MailError('Compose')
        
    def OnForward(self, event):
        """Create a forward page in the notebook"""
        d, vis = self.database_handler.GetCurrentMailbox()
        if d:
            data = vis.GetCurrentMailData()
            new_subject = 'Fwd: ' + data['Header']['Subject']
            new_from = d.username + '@' + d.domain
            body = data['Body']
            att = data['Attachments']
            mail = data['Mail']
            mailbox = data['Mailbox']
            vis.NewIncompleteMail(from_who = new_from, isubject = new_subject, body = body, attachments = att, mailbox = d, mail = mail,
                                  mailbox = mailbox)
        else:
            self.MailError('Forward')
        
    def OnSend(self, event):
        """Send the current message"""
        domain, vis = self.database_handler.GetCurrentMailbox()
        if domain:
            data = vis.GetCurrentMailData()
            fromt = data['Header']['From']
            tot = data['Header']['To']
            bcct = data['Header']['Bcc']
            cct = data['Header']['Cc']
            subjectt = data['Header']['Subject']
            source = data['Body']
            att = data['Attachments']
            mail = data['Mail']
            mailbox = data['Mailbox']
            mail.orig_date = datetime.datetime.now()
            mail.subject = subjectt
            mail.bcc = bcct
            mail.cc = cct
            mail.from_who = fromt
            mail.to = tot
            mail.body = source
            mail.attachments = att
            
            # Mail iterators
            cc_list = [tot]
            bcc_list = []
            if cct:
                cc_list = cc_list + self.SplitEmail(cct)
            if bcct:
                bcc_list =  bcc_list + self.SplitEmail(bcct)
       
            bit_control = {}
            for k in range(len(bcc_list) + 1):
                bit_control[k] = (False, 'No Feedback')
    
            # Construct message and send with twisted
            message = MIMEMultipart('related')
            message['From'] = fromt
            message['To'] = tot
            message['Subject'] = subjectt
        
            altmessage = MIMEMultipart('alternative')
            text = MIMEText(source, _subtype='plain', _charset = 'iso-8859-1')
            altmessage.attach(text)
            html = MIMEText(markdown.markdown(source), _subtype='html', _charset='iso-8859-1')
            altmessage.attach(html)
            message.attach(altmessage)
        
            # Iterate over the attachemts
            self.AddAttachments(message, att)
            
            # Send the data
            self.database_handler.BeginSend(mail)
            self.SendMail(mail, message, bit_control, 0, domain, fromt, cc_list, mailbox)
            for index, address in enumerate(bcc_list):
                if address:
                    self.SendMail(mail, message, bit_control, index + 1, domain, fromt, address, mailbox)
        else:
            self.MailError('Send')

    def SendMail(self, mail, message, bit_control, index, domain, fromt, cc_list, mailbox):
        """Send the mail to the relevant server"""
        d = defer.Deferred().addBoth(self.HandleSendOutput, mail = Mail(*mail.get()), mailbox = mailbox,
                                     bit_control = bit_control, index = index)
        messageData = message.as_string(unixfrom = False)
        f = smtp.ESMTPSenderFactory(domain.username, domain.password, fromt, cc_list, StringIO(str(messageData)), d)
        if domain.ossl:
            reactor.connectSSL(str(domain.oserver), domain.oport, f, ssl.ClientContextFactory())
        else:
            reactor.connectTCP(str(domain.oserver), domain.oport, f)
        
    def AddAttachments(self, message, att):
        """Add attachments to the multipart message"""
        for a in att:
            main, sub = a.encoding.split('/')
            attachment = MIMEBase(main, sub)
            attachment.set_payload(a.data)
            Encoders.encode_base64(attachment)
            attachment.add_header('Content-Disposition', 'attachment', filename = a.title)
            message.attach(attachment)
            
    def HandleSendOutput(self, data, **kwargs):
        """Handle the reply from the mailserver"""
        bit_control = kwargs['bit_control']
        index = kwargs['index']
        mail = kwargs['mail']
        mailbox = kwargs['mailbox']
        print data, bit_control, index, mail
        if isinstance(data, Failure):
            bit_control[index] = (False, 'Feedback')
        elif '2.1.5 OK' in data[1][0][2]:
            bit_control[index] = (True, 'Feedback')
        else:
            bit_control[index] = (False, 'Feedback')
            
        if self.CompleteFeedback(bit_control):
            self.database_handler.EndSend(mail, mailbox, self.SuccesfulFeedback(bit_control))
            
    def CompleteFeedback(self, bit_control):
        """Checks if all of the feedback is back from the server"""
        for key, value in bit_control.iteritems():
            if isinstance(value, Mail):
                continue
            if value[1] == self.no_feedback:
                return False
        return True
    
    def SuccesfulFeedback(self, bit_control):
        """Checks if the was succesfully sent"""
        for key, value in bit_control.iteritems():
            if isinstance(value, Mail):
                continue
            if not value[0]:
                return False
        return True
    
    def OnReply(self, event):
        """Reply the current message"""
        d, vis = self.database_handler.GetCurrentMailbox()
        if d:
            data = vis.GetCurrentMailData()
            new_subject = 'Re: ' + data['Header']['Subject']
            new_from = d.username + '@' + d.domain
            new_to = data['Header']['From']
            body = data['Body']
            att = data['Attachments']
            mail = data['Mail']
            mailbox = data["Mailbox"]
            vis.NewIncompleteMail(from_who = new_from, isubject = new_subject, body = body, attachments = att, ito = new_to,
                                  mailbox = d, mail = mail, mailbox = mailbox)
        else:
            self.MailError('Reply')
        
    def OnReplyAll(self, event):
        """Reply to the current message, and everyone attached to the email"""
        d, vis = self.database_handler.GetCurrentMailbox()
        if d:
            data = vis.GetCurrentMailData()
            new_subject = 'Re: ' + data['Header']['Subject']
            new_from = d.username + '@' + d.domain
            new_to = data['Header']['From']
            new_bcc = data['Header']['Bcc']
            new_cc = data['Header']['Cc']
            body = data['Body']
            att = data['Attachments']
            mail = data['Mail']
            mailbox = data['Mailbox']
            vis.NewIncompleteMail(from_who = new_from, isubject = new_subject, body = body, attachments = att, ito = new_to,
                                       icc = new_cc, ibcc = new_bcc, mailbox = d, mail = mail, mailbox = mailbox)
        else:
            self.MailError('ReplyAll')
            
    def MailError(self, str):
        wx.MessageBox("No Mailboxes have been set up for KuplaMail. Please press 'New' in the toolbar"
                      " or, Ctrl-N to initialize a mailbox, so that you can %s appropriately." % str, style = wx.OK | wx.ICON_EXCLAMATION)
    def SplitEmail(self, mail_list):
        """Split the email into relevant portions"""
        if mail_list.find(',') != -1:
            return [m.strip() for m in mail_list.split(',') if len(m)]
        else:
            return [mail_list.strip()]
             
class MailHandler(object):
    """Format the email to work using the native browser renderer"""
    def __init__(self):
        self.pre = ['<pre>', '</pre>']
        self.a = ['<a href="%s">', '</a>']
        self.htmlstart = ['<html>', '<HTML>']
        self.htmlend = ['</html>', '</HTML>']
        self.ret = []
        self.start = """<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\n<html>\n<head>\n<title>\n</title></head><body>"""
        self.end = """</body></html>"""
        
    def format_email(self, istr):
        """Format that the email so that it will display properly in a browser frame"""
        try:
            string = str(istr)
            hlength = len(self.htmlstart[0])
            if string:
                if len(string) < 2 * hlength:
                    return str(string)
                else:
                    # Check if it is an html document
                    if string[:hlength] in self.htmlstart and string[hlength:] in self.htmlend:
                        return str(string)
                    else:
                        index = 0
                        lastindex = 0
                        self.ret = []
                        self.ret.append(self.start)

                        string = self.parse_string(string)  
                        while True:
                            lastindex = index
                            index = string.find('http://', index)
                            if index == -1:
                                self.preiffy(string[lastindex:])
                                break
                            else:
                                self.preiffy(string[lastindex:index])
                                # Find 'http://.... ' that last space
                                index2 = string.find(' ', index)
                                index3 = string.find('\n', index)
                                if index3 < index2:
                                    index2 = index3
                                if index2 != -1:
                                    self.aiffy(string[index:index2])
                                else:
                                    self.aiffy(string[index:])
                                    break
                                index = index2
                        self.ret.append(self.end)
                        return ''.join(self.ret)
            else:
                return ''
        except Exception, e:
            print e

    def parse_string(self, string):
        """Parse the current string for =%h values"""
        index = 0
        step = 0
        l = len(string)
        ret = []
        while True:
            pindex = index
            index = string.find('=', index)
            ret.append(string[pindex:index])
            if index < l - 3 and index != -1:
                try:
                    n = int(string[index + 1: index + 3], 16)
                    ret.append(chr(n))
                    step = 3
                except ValueError, e:
                    if string[index + 1] == '\n':
                        ret.append('\n')
                        step = 2
                    if string[index + 1] == '=':
                        ret.append('=')
                        step = 1
                    elif string[index + 1].isalpha():
                        ret.append(string[index: index +3])
                        step = 4
                    else:
                        step = 1
            else:
                ret.append(string[index:])
                break
            index = index + step
        return ''.join(ret)

    def aiffy(self, string):
        """Create a link out of the substring"""
        self.ret.append(self.a[0]% string)
        self.ret.append(string)
        self.ret.append(self.a[1])

    def preiffy(self, string):
        """Place <pre> around text so that the email is formatted correctly"""
        self.ret.append(self.pre[0])
        self.ret.append(string)
        self.ret.append(self.pre[1])